package com.panopset.net.ftp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Vector;

import com.panopset.Strings;

import static com.panopset.Util.*;

/**
 * FTP Command socket.
 *
 * @author Karl Dinwiddie
 *
 */
public final class FTPCommandSocket {

    /**
     * Socket wrapper.
     */
    private final SocketWrapper sw;

    /**
     * FTP command port is 21.
     */
    public static final int FTP_CMD_PORT = 21;

    /**
     * Reader.
     */
    private BufferedReader reader;

    /**
     * Writer.
     */
    private BufferedWriter writer;

    /**
     * FTPCommandSocket constructor.
     *
     * @param host
     *            FTP host address.
     * @throws IOException
     *             IO Exception.
     */
    FTPCommandSocket(final String host) throws IOException {
        sw = new SocketWrapper(host, FTP_CMD_PORT);
        dspmsg("Connected to " + host);
        String response = readLine();
        if (!response.startsWith("220 ")) {
            throw new IOException(response);
        }
    }

    /**
     * Close the data socket.
     *
     * @throws IOException
     *             IO Exception.
     */
    public void close() throws IOException {
        sw.close();
    }

    /**
     * Send verify.
     *
     * @param command
     *            FTP command to verify.
     * @param result
     *            Result.
     * @return Response.
     * @throws IOException
     *             IO Exception.
     */
    String sendVerify(final String command, final String result)
            throws IOException {
        return sendVerify(command, new String[] {result });
    }

    /**
     * Send verify.
     *
     * @param command
     *            FTP command to verify,.
     * @param results
     *            Results.
     * @return Response.
     * @throws IOException
     *             IO Exception.
     */
    String sendVerify(final String command, final String... results)
            throws IOException {
        String r = getCommandResponse(command);
        for (String s : results) {
            if (r.startsWith(s)) {
                return r;
            }
        }
        throw new IOException("Unexpected result: " + r);
    }

    /**
     * @return BufferedReader.
     * @throws IOException
     *             IO Exception.
     */
    BufferedReader getReader() throws IOException {
        if (this.reader == null) {
            reader = new BufferedReader(new InputStreamReader(sw.getSocket()
                    .getInputStream()));
        }
        return this.reader;
    }

    /**
     * @return BufferedWriter.
     * @throws IOException
     *             IO Exception.
     */
    BufferedWriter getWriter() throws IOException {
        if (this.writer == null) {
            writer = new BufferedWriter(new OutputStreamWriter(sw.getSocket()
                    .getOutputStream()));
        }
        return this.writer;
    }

    /**
     * Get command response.
     *
     * @param command
     *            Command line to send.
     * @return Response.
     * @throws IOException
     *             IO Exception.
     */
    String getCommandResponse(final String command) throws IOException {
        sendCommandLine(command);
        return readLine();
    }

    /**
     * Read line.
     *
     * @return Command line response.
     * @throws IOException
     *             IO Exception.
     */
    String readLine() throws IOException {
        Vector<String> lines = getCommandResponses();
        if (lines == null) {
            throw new IOException("null response.");
        }
        if (lines.size() < 1) {
            dspmsg("No response");
            return "";
        }
        for (int i = 0; i < lines.size() - 1; i++) {
            dspmsg("Ignoring message: " + lines.elementAt(i));
        }
        return lines.elementAt(lines.size() - 1);
    }

    /**
     * Send command line.
     *
     * @param line
     *            Command line to send.
     * @throws IOException
     *             IO Exception.
     */
    void sendCommandLine(final String line) throws IOException {
        getWriter().write(line + Strings.getEol());
        getWriter().flush();
        dspmsg("> " + line);
    }

    /**
     * Send command.
     *
     * @param line
     *            Command line to send.
     * @return Responses.
     * @throws IOException
     *             IO Exception.
     */
    Vector<String> sendCommand(final String line) throws IOException {
        sendCommandLine(line);
        dspmsg("> " + line);
        return getCommandResponses();
    }

    /**
     * Command response sleep time is 100.
     */
    private static final int COMMAND_RESPONSE_SLEEP_TIME = 100;

    /**
     * Maximum number of responses to get is 20.
     */
    private static final int MAX_RESPONSES = 20;

    /**
     * Get command responses.
     *
     * @return All command responses up to MAX_RESPONSES.
     * @throws IOException
     *             IO Exception.
     */
    Vector<String> getCommandResponses() throws IOException {
        Vector<String> rtn = new Vector<String>();
        int count = 0;
        while (!getReader().ready() && count++ < MAX_RESPONSES) {
            try {
                Thread.sleep(COMMAND_RESPONSE_SLEEP_TIME);
            } catch (InterruptedException e) {
                log(e);
            }
        }
        while (getReader().ready()) {
            rtn.add(getReader().readLine());
        }
        for (String s : rtn) {
            dspmsg("< " + s);
        }
        return rtn;
    }
}
